package estrutura;

/**
 *
 * @author ra106026, cdvdt
 */
import java.util.Set;
import java.util.LinkedList;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultEdge;

/**
 * Armazena os caminhos críticos para cada disciplina do grafo associado.
 * <p>
 * Para garantir que todas as disciplinas do grafo foram atingidas é necessario
 * executar o método {@link #calcula(Materias) } para cada disciplina sem
 * pré-requisito. Caso o grafo mude, será necessário criar outro objeto da
 * classe {@code Caminho }.
 *
 * @author cdvdt
 */
public class Caminho {

    /**
     * grafo associado a Caminho
     */
    private DefaultDirectedGraph<Materias, DefaultEdge> grafo;
    /**
     * lista de disciplinas cujo comprimento do caminho já foi calculado
     */
    private LinkedList<CaminhoMateria> listaDisciplinas = new LinkedList<>();

    /**
     * Cria um novo Caminho associado a um grafo
     *
     * @param grafo grafo associado a um novo objeto Caminho
     */
    public Caminho(DefaultDirectedGraph<Materias, DefaultEdge> grafo) {
        this.grafo = grafo;
    }

    /**
     * Classe que associa um caminho calculado a uma matéria
     */
    private class CaminhoMateria {

        /**
         * Disciplina a ser associada ao comprimento do caminho calculado
         */
        private final Materias disciplina;
        /**
         * Comprimento do caminho calculado
         */
        private final int comprimento;

        /**
         * Cria um CaminhoMateria associando uma matéria a um caminho calculado
         *
         * @param disciplina matéria a ser associado a um tamanho de caminho
         * @param comprimento comprimento do caminho
         */
        private CaminhoMateria(Materias disciplina, int comprimento) {
            this.disciplina = disciplina;
            this.comprimento = comprimento;
        }
    }

    /**
     * Adiciona disciplinas a listaDisciplinas
     *
     * @param disciplina disciplina a ser adicionada
     */
    private void addListaDisciplinas(CaminhoMateria disciplina) {
        listaDisciplinas.add(disciplina);
    }

    /**
     * Retorna o comprimento do caminho de uma dada disciplina
     *
     * @param disciplina disciplina a ser retornado o caminho
     * @return comprimento do caminho para dada disciplina, ou -1 caso não
     * esteja presente em listaDisciplinas
     */
    private int getComprimentoFromDisciplina(Materias disciplina) {
        for (CaminhoMateria node : listaDisciplinas) {
            if (node.disciplina.equals(disciplina)) {
                return node.comprimento;
            }
        }
        return -1;
    }

    /**
     * Calcula o tamanho do maior caminho no grafo, partindo de uma dada
     * disciplina
     *
     * @param disciplina disciplina inicial para o calculo do caminho
     * @return int maior caminho no grafo a partir de uma dada disciplina
     */
    public int calcula(Materias disciplina) {
        /*se a disciplina não é prereq retorna 0*/
        int compCaminho = getComprimentoFromDisciplina(disciplina);
        if (compCaminho != -1) {
            return compCaminho;
        }
        if (grafo.outDegreeOf(disciplina) == 0) {
            addListaDisciplinas(new CaminhoMateria(disciplina, 0));
            return 0;
        } else {
            int i = 0;
            int j;
            Set<DefaultEdge> edges = grafo.outgoingEdgesOf(disciplina);
            for (DefaultEdge k : edges) {
                j = calcula(grafo.getEdgeTarget(k));
                i = (i < j ? j : i);
            }
            i++;
            addListaDisciplinas(new CaminhoMateria(disciplina, i));
            return i;
        }
    }
}
